#include "CDGlib.h"

CDGlib::CDGlib()
{
    trans_trans = true;
    trans_bg = true;
    trans_border = true;
    buffer = NULL;
    clear();
}

CDGlib::~CDGlib()
{
    stop();
}

bool CDGlib::start(const std::string& filename, bool useTransp, bool useTransBkg, bool useTransBorder)
{
    FILE *fd; 
    trans_trans = useTransp;
    trans_bg = useTransBkg;
    trans_border = useTransBorder;

    clear();

    printf("Open file: %s\n",filename.c_str());
    fd = fopen(filename.c_str(), "rb");
    if (fd==NULL)
    {
        printf("Couldnt open file\n");
        return false;
    }

    // Obtain file size
    fseek(fd, 0, SEEK_END);
    buffSize = ftell(fd);
    rewind(fd);
    
    // Allocate memory to contain the file
    buffer = (char*) malloc(buffSize);
    if (buffer == NULL)
    {
        fclose(fd);
        printf("Memory error\n");
        return false;
    }
    
    // Copy the file into the buffer:
    size_t result = fread(buffer,1,buffSize,fd);
    if (result != buffSize)
    {
        fclose(fd);
        free(buffer);
        printf("Reading error\n");
        return false;
    }

    parseptr = 0;
    fclose(fd);
    fd = NULL;
    return true;
}

void CDGlib::stop()
{
    if(buffer)
    {
        free(buffer);
        buffer = NULL;
    }
}

void CDGlib::clear()
{
    //Reset image
    memset(image, 0, sizeof(image));

    //Reset frame
    memset(frame, 0, sizeof(frame));

    //Reset colormap
    memset(colormap, 0, sizeof(colormap));

    // Reset colors
    memset(colors, 0, sizeof(colors));

    color_trans = -1;
    color_bg = -1;
    color_border = -1;

    buffSize = 0;
    parseptr = 0;

    resetUpdateArea();    
}

void CDGlib::switchTransparency()
{
    printf("switchTransparency\n");
    trans_trans = !trans_trans;
}
void CDGlib::switchTransparentBkg()
{
    printf("switchTransparentBkg\n");
    trans_bg = !trans_bg;
}

void CDGlib::generateImage()
{
    for (int y = 0; y < cdgImageHeight; ++y) {
        for (int x = 0; x < cdgImageWidth; ++x) {
            //Generate image
            image[y][x][0] = colors[colormap[y][x]][0];
            image[y][x][1] = colors[colormap[y][x]][1];
            image[y][x][2] = colors[colormap[y][x]][2];
            image[y][x][3] = colors[colormap[y][x]][3];
        }
    }
}

void CDGlib::generateFrame()
{
    //Reset frame
    memset(frame, 0, sizeof(frame));
    
    for (int y = 0; y < cdgImageHeight; ++y) {
        for (int x = 0; x < cdgImageWidth; ++x) {
            if(image[y][x][3] == 0)
            {
                if(((y>0) &&(y<216)) && (image[y-1][x][3] != 0)  || //Up
                   ((y>=0)&&(y<215)) && (image[y+1][x][3] != 0)  || //Down
                   ((x>0) &&(x<300)) && (image[y][x-1][3] != 0)  || //Left
                   ((x>=0)&&(x<299)) && (image[y][x+1][3] != 0))    //Right
                {
                    // Fill since near a non-transparent pixel
                    frame[y][x][0] = colors[color_bg][0];
                    frame[y][x][1] = colors[color_bg][1];
                    frame[y][x][2] = colors[color_bg][2];
                    frame[y][x][3] = 0xFF;
                }
            }
            else
            {
                // Also fill behind image to avoid glitches
                frame[y][x][0] = colors[color_bg][0];
                frame[y][x][1] = colors[color_bg][1];
                frame[y][x][2] = colors[color_bg][2];
                frame[y][x][3] = 0xFF;
            }
        }
    }
}

// Shall handle 300 pkg/s (=75sectorer/s * 4pkg/sector)
void CDGlib::handlePackets(int amount)
{
    // Read a packages
    for(int i = 0; i<amount;++i,++parseptr)
    {
        if((sizeof(SubCode_t)*parseptr) >= buffSize)
        {
            printf("End of CDG-file.\n");
            return;
        }
        SubCode_t subCode;
        memcpy(&subCode,&buffer[sizeof(SubCode_t)*parseptr],sizeof(SubCode_t));

        if ((subCode.command & SC_MASK) == SC_CDG_COMMAND) {
            switch (subCode.instruction & SC_MASK) {
                case CDG_MEMORYPRESET:  MemoryPreset((const CDG_MemPreset_t&)subCode.data[0]);    break;
                case CDG_BORDERPRESET:  BorderPreset((const CDG_BorderPreset_t&)subCode.data[0]); break;
                case CDG_TILEBLOCK:     TitleBlock(false,(const CDG_Tile_t&)subCode.data[0]);     break;
                case CDG_SCROLLPRESET:  ScrollPreset((const CDG_Scroll_t&)subCode.data[0]);       break;
                case CDG_SCROLLCOPY:    ScrollCopy((const CDG_Scroll_t&)subCode.data[0]);         break;
                case CDG_DEFINETRANS:   DefineTrans((const CDG_Transparent_t&)subCode.data[0]);   break;
                case CDG_LOADCOLOR0_7:  LoadColor(0,(const CDG_LoadCLUT_t&)subCode.data[0]);      break;
                case CDG_LOADCOLOR8_15: LoadColor(8,(const CDG_LoadCLUT_t&)subCode.data[0]);      break;
                case CDG_TILEBLOCK_XOR: TitleBlock(true,(const CDG_Tile_t&)subCode.data[0]);      break;
                default:
                     printf("Unknown instruction!\n");
            }
        }
    }
}

void CDGlib::MemoryPreset(const CDG_MemPreset_t& data)
{
    if((data.repeat & 0x0F)==0)
    {
        int color = data.color & 0x0F; 
        if(color != color_bg)
        {
            //Set new background and update old
            if(trans_bg) 
               colors[color][3] = 0x00;
            if((color_bg>-1) && (color_bg != color_trans) && (color_bg != color_border))
               colors[color_bg][3] = 0xFF;
            color_bg = color;
        }
        fillArea(color, 0, 0,cdgImageWidth,cdgImageHeight);
        
        printf("Memory Preset Color %d to RGB: %d:%d:%d:%d\n",
               color,
               colors[color][0],
               colors[color][1],
               colors[color][2],
               colors[color][3]
        );
    }
//     else
//     {
//         printf("Memory Preset, repeat=%d SKIPPING\n",data.repeat & 0x0F);
//     }
}

void CDGlib::BorderPreset(const CDG_BorderPreset_t& data)
{
    int color = data.color & 0x0F;    
    if(color != color_border)
    {
        //Set new background and update old
        if(trans_border) 
           colors[color][3] = 0x00;
        if((color_border>-1) && (color_border != color_trans) && (color_border != color_bg))
           colors[color_border][3] = 0xFF;
        color_border = color;
    }

    //Total: (0,0,300,216)
    //Image: (6,12,294,204)
    fillArea(color, 0,    0, cdgImageWidth,12);     //Top
    fillArea(color, 0,  204, cdgImageWidth,cdgImageHeight);  //Bottom
    fillArea(color, 0,   12, 6,204);    //Left
    fillArea(color, 295, 12, cdgImageWidth,204); //Right

    printf("Border Preset Color %d to RGB: %d:%d:%d:%d\n",
           color,
           colors[color][0],
           colors[color][1],
           colors[color][2],
           colors[color][3]
    );
}


void CDGlib::ScrollPreset(const CDG_Scroll_t& data)
{
    Scroll(data,false);
}

void CDGlib::ScrollCopy(const CDG_Scroll_t& data)
{
    Scroll(data,true);
}


void CDGlib::Scroll(const CDG_Scroll_t& data, bool copy)
{
    int color = data.color & 0x0F;
    int hSCmd = ((data.hScroll & 0x3F) & 0x30) >> 4;
    int hOffset = (data.hScroll & 0x3F) & 0x07;
    int vSCmd = ((data.vScroll & 0x3F) & 0x30) >> 4;
    int vOffset = (data.vScroll & 0x3F) & 0x0F;

    printf("Scroll color=%d, hSCmd=%d,hOffset=%d, vSCmd=%d,vOffset=%d\n",
           color, hSCmd, hOffset, vSCmd, vOffset);

    if((hSCmd==0) && (vSCmd==0))
       return;

    unsigned char scrollmap[cdgImageHeight][cdgImageWidth];
    
    if (hSCmd == 2)      //Left
       hOffset = -6;
    else if (hSCmd == 1) //Right
       hOffset = 6;

    if (vSCmd == 2)      //Up
        vOffset = -12;
    else if (vSCmd == 1) //Down
        vOffset = 12;

    // Scroll image
    for(int y=0;y<cdgImageHeight;++y)
    {
        for(int x=0;x<cdgImageWidth;++x)
        {
            scrollmap[(y+cdgImageHeight+vOffset)%cdgImageHeight][(x+cdgImageWidth+hOffset)%cdgImageWidth] = colormap[y][x];
        }
    }   
 
    //Add new color
    if(!copy)
    {
        //Down
        if(vOffset>0)
        {
            for(int y=0;y<vOffset;++y)
               for(int x=0;x<cdgImageWidth;++x)
                  scrollmap[y][x] = color;
        }
        //Up
        if(vOffset<0)
        {
            for(int y=(cdgImageHeight+vOffset);y<cdgImageHeight;++y)
               for(int x=0;x<cdgImageWidth;++x)
                  scrollmap[y][x] = color;
        }
        //Right
        if(hOffset>0) 
        {
            for (int y = 0; y < cdgImageHeight; ++y)
               for (int x = 0; x < hOffset; ++x)
                  colormap[y][x] = color;
        }
        //Left
        if(hOffset<0) 
        {
            for (int y = (cdgImageHeight+hOffset); y < cdgImageHeight; ++y)
               for (int x = 0; x < cdgImageWidth; ++x)
                  colormap[y][x] = color;
        }
    }

    // Copy scrolled image
    for (int y = 0; y < cdgImageHeight; ++y) {
        for (int x = 0; x < cdgImageWidth; ++x) {
            colormap[y][x] = scrollmap[y][x];
        }
    }

    setUpdateArea(0,0,cdgImageWidth,cdgImageHeight);
}


void CDGlib::DefineTrans(const CDG_Transparent_t& data)
{
    int color = data.color & 0x0F;

    if(color != color_trans)
    {
        //Set new background and update old
        if(trans_trans)
           colors[color][3] = 0x00;
        if((color_trans>-1) && (color_trans != color_bg) && (color_trans != color_border))
           colors[color_trans][3] = 0xFF;
        color_trans = color;
    }
    
    printf("Define Transparent Color %d to RGB: %d:%d:%d:%d\n",
           color,
           colors[color][0],
           colors[color][1],
           colors[color][2],
           colors[color][3]
    );

    setUpdateArea(0,0,cdgImageWidth,cdgImageHeight);
}

void CDGlib::LoadColor(int offset, const CDG_LoadCLUT_t& data)
{
//     printf("Load Colors %d-%d\n",offset,offset+7);

    // Read all 8 colors
    for(int i=0;i<8;++i)
    {
        // Multiply with 17 to convert 4bit colors to 8bit (0x0F*17=255)
        short tmp = data.colorSpec[i];

//      |Lowbyte |Highbyte|
//      |XXggbbbb|XXrrrrgg|
        colors[i+offset][0] = 17*(unsigned int)((tmp & 0x003C)>>2);
        colors[i+offset][1] = 17*((unsigned int)((tmp<<2)&0x000C)) + ((unsigned int)((tmp>>12)&0x0003));
        colors[i+offset][2] = 17*(unsigned int)((tmp & 0x0F00)>>8);
        colors[i+offset][3] = 0xFF;

        if(trans_bg && ((i+offset) == color_trans)) 
           colors[i+offset][3] = 0x00;
        if(trans_trans && ((i+offset) == color_bg)) 
           colors[i+offset][3] = 0x00;
        if(trans_trans && ((i+offset) == color_border)) 
           colors[i+offset][3] = 0x00;
                
        printf("Converting color %d %04x to RGB: %d:%d:%d:%d\n",
               i+offset, tmp,
               colors[i+offset][0],
               colors[i+offset][1],
               colors[i+offset][2],
               colors[i+offset][3]
        );
    }
    setUpdateArea(0,0,cdgImageWidth,cdgImageHeight);
}

void CDGlib::TitleBlock(bool XOR, const CDG_Tile_t& data)
{
//     printf("TitleBlock XOR=%d, color0=%d,color1=%d,row=%d,column=%d,tilePixels=...\n", 
//            XOR,
//            data.color0 & 0x0F,
//            data.color1 & 0x0F,
//            data.row & 0x1F,
//            data.column & 0x3F);
    int col = ((data.column & 0x3F)*6);
    int row = ((data.row & 0x1F)*12);

    if((col>=cdgImageWidth)||(row>=cdgImageHeight)) {
        printf("ERROR: Error in cmd TileBlock: col=%d (<300) row=%d (<216)\n",col,row);
        return;
    }

    setUpdateArea(col,row,col+6,row+12);

    for(int y=row;y<(row+12);++y)
    {        
        for(int x=col;x<(col+6);++x)
        {    
            if(((data.tilePixels[y-row] & 0x3F)>>(5-(x-col))) & 0x01)
            {
                // 1
                if(XOR)
                {
                    int color = data.color1 & 0x0F;
                    colormap[y][x] ^=  color;
                }
                else
                {
                    int color = data.color1 & 0x0F;
                    colormap[y][x] = color;
                }
            }
            else
            {
                // 0
                if(XOR) 
                {
                    int color = data.color0 & 0x0F;
                    colormap[y][x] ^=  color;
                }
                else
                {
                    int color = data.color0 & 0x0F;
                    colormap[y][x] = color;
                }
            }
        }
    }
}

void CDGlib::fillArea(int color, int topLeftX, int topLeftY, int bottomRightX, int bottomRightY)
{
//     printf("Filling area with color %d (RGB:%d %d %d)\n",
//            color,
//            colors[color].r,
//            colors[color].g,
//            colors[color].b);
    for(int y=topLeftY;y<bottomRightY;++y)
    {
        for(int x=topLeftX;x<bottomRightX;++x)
        {
            colormap[y][x] = color;
        }
    }
    setUpdateArea(topLeftX,topLeftY,bottomRightX,bottomRightY);
}

void CDGlib::setUpdateArea(int topLeftX, int topLeftY, int bottomRightX, int bottomRightY)
{
    if((-1==area.tl_x)||(topLeftX<area.tl_x)) area.tl_x = topLeftX;
    if((-1==area.tl_y)||(topLeftY<area.tl_y)) area.tl_y = topLeftY;
    
    if(bottomRightX>area.br_x) area.br_x = bottomRightX; 
    if(bottomRightY>area.br_y) area.br_y = bottomRightY; 

    if(bottomRightX>area.br_x) area.br_x = bottomRightX; 
    if(bottomRightY>area.br_y) area.br_y = bottomRightY; 
}

void CDGlib::resetUpdateArea()
{
    area.tl_x = -1;
    area.tl_y = -1;
    area.br_x = -1;
    area.br_y = -1;
}

void CDGlib::getUpdateArea(CDG_Area_t& getarea)
{
    getarea.tl_x = area.tl_x;
    getarea.tl_y = area.tl_y;
    getarea.br_x = area.br_x;
    getarea.br_y = area.br_y;
    resetUpdateArea();

}
